package com.naiterui.ehp.bs.patient.service.impl;

import java.math.BigInteger;
import java.util.Date;
import java.util.HashMap;
import java.util.List;
import java.util.Map;

import org.apache.commons.collections4.CollectionUtils;
import org.apache.commons.lang3.StringUtils;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.beans.BeanUtils;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.beans.factory.annotation.Value;
import org.springframework.scheduling.annotation.Async;
import org.springframework.stereotype.Service;
import org.springframework.transaction.annotation.Transactional;

import com.naiterui.common.redis.RedisUtil;
import com.naiterui.ehp.bp.bo.doctor.RecomPatientBO;
import com.naiterui.ehp.bp.bo.emr.AddPatientRecordInitializeBO;
import com.naiterui.ehp.bp.bo.emr.PatientDiseaseBO;
import com.naiterui.ehp.bp.bo.im.MsgSendParamBO;
import com.naiterui.ehp.bp.bo.im.PatientMsgSendParamBO;
import com.naiterui.ehp.bp.bo.patient.MsgMaPublishParamBO;
import com.naiterui.ehp.bp.bo.patient.PatientBO;
import com.naiterui.ehp.bp.bo.patient.PatientBaseBO;
import com.naiterui.ehp.bp.bo.patient.PatientInquirerBO;
import com.naiterui.ehp.bp.bo.patient.RequireReplyTemplateMsgBO;
import com.naiterui.ehp.bp.constants.ImConstants;
import com.naiterui.ehp.bp.constants.InquirerRelationEnum;
import com.naiterui.ehp.bp.domain.City;
import com.naiterui.ehp.bp.domain.ConsultSession;
import com.naiterui.ehp.bp.domain.Department;
import com.naiterui.ehp.bp.domain.Doctor;
import com.naiterui.ehp.bp.domain.DoctorCharge;
import com.naiterui.ehp.bp.domain.Patient;
import com.naiterui.ehp.bp.domain.PatientInquirerInfo;
import com.naiterui.ehp.bp.domain.PatientPersonDoctor;
import com.naiterui.ehp.bp.domain.PatientSuggestion;
import com.naiterui.ehp.bp.support.context.ResourceHolder;
import com.naiterui.ehp.bp.support.exception.BusinessException;
import com.naiterui.ehp.bp.support.utils.JwtHelper;
import com.naiterui.ehp.bp.support.utils.JwtUser;
import com.naiterui.ehp.bp.utils.date.DateUtil;
import com.naiterui.ehp.bp.utils.encypt.DESPlus;
import com.naiterui.ehp.bp.utils.lang.ConfigUtil;
import com.naiterui.ehp.bs.patient.bo.notice.RegisterParamBO;
import com.naiterui.ehp.bs.patient.constants.CacheConstant;
import com.naiterui.ehp.bs.patient.dao.IDoctorDao;
import com.naiterui.ehp.bs.patient.dao.IPatientDao;
import com.naiterui.ehp.bs.patient.dao.IPatientPersonDoctorDao;
import com.naiterui.ehp.bs.patient.dao.mongo.ApMessageDao;
import com.naiterui.ehp.bs.patient.exception.ExceptionCodes;
import com.naiterui.ehp.bs.patient.feign.EmrFeginClient;
import com.naiterui.ehp.bs.patient.feign.ImFeginClient;
import com.naiterui.ehp.bs.patient.repository.IApCityRepository;
import com.naiterui.ehp.bs.patient.repository.IApConsultSessionRepository;
import com.naiterui.ehp.bs.patient.repository.IApDepartmentRepository;
import com.naiterui.ehp.bs.patient.repository.IApDoctorChargeRepository;
import com.naiterui.ehp.bs.patient.repository.IApPatientRepository;
import com.naiterui.ehp.bs.patient.repository.IApPatientSuggestionRepository;
import com.naiterui.ehp.bs.patient.repository.IPatientInquirerRepository;
import com.naiterui.ehp.bs.patient.service.IPatientDoctorService;
import com.naiterui.ehp.bs.patient.service.IPatientFamilyService;
import com.naiterui.ehp.bs.patient.service.IPatientInquirerService;
import com.naiterui.ehp.bs.patient.service.IPatientRecordService;
import com.naiterui.ehp.bs.patient.service.IPatientService;
import com.naiterui.ehp.bs.patient.service.IPatientWeixinLogService;
import com.naiterui.ehp.bs.patient.service.ISubscribeNoticeService;
import com.naiterui.ehp.bs.patient.service.ITemplateNoticeService;
import com.naiterui.ehp.bs.patient.service.message.IMessageSendService;
import com.naiterui.ehp.bs.patient.utils.ConfigKeyUtil;
import com.naiterui.ehp.bs.patient.utils.NoticeUtil;
import com.naiterui.ehp.bs.patient.utils.StringUtil;
import com.naiterui.ehp.bs.patient.utils.cache.BaseParamCacheUtil;
import com.naiterui.ehp.bs.patient.utils.cache.PatientCacheUtil;
import com.naiterui.ehp.bs.patient.utils.constants.PatientConstant;
import com.naiterui.ehp.bs.patient.utils.exception.PatientBusinessException;
import com.naiterui.ehp.bs.patient.vo.DeviceVO;
import com.naiterui.ehp.bs.patient.vo.DeviceVerifyVO;
import com.naiterui.ehp.bs.patient.vo.im.Message.Extend;
import com.naiterui.ehp.bs.patient.vo.inner.InnerPatientVO;
import com.naiterui.ehp.bs.patient.vo.parameters.DepartmentVO;
import com.naiterui.ehp.bs.patient.vo.patient.PatientVO;
import com.naiterui.ehp.bs.patient.vo.requestparam.PatientBaseParam;
import com.naiterui.ehp.bs.wechat.service.ICustomMsgApiService;
import com.naiterui.ehp.bs.wechat.service.IWebAuthorizeApiService;

import cn.hutool.core.collection.CollectionUtil;
import cn.hutool.core.util.IdcardUtil;
import cn.hutool.core.util.StrUtil;
import cn.hutool.json.JSONUtil;
import lombok.RequiredArgsConstructor;
import lombok.extern.slf4j.Slf4j;

/**
 * 患者基础业务逻辑实现
 *
 * @version 2015年6月18日 下午5:07:56 by Amber
 * @Description <详细描述>
 * @see IPatientService
 */
@Slf4j
@Service
@RequiredArgsConstructor
public class ApPatientServiceImpl implements IPatientService {

    private static final Logger LOGGER = LoggerFactory.getLogger(ApPatientServiceImpl.class);

    @Value("${wx.miniapp.appid}")
    private String wxMiniappAppid;
    @Value("${wx.thumbMediaId}")
    private String thumbMediaId;

    @Autowired
    private IPatientDao patientDao;
    @Autowired
    private IDoctorDao doctorDao;
    @Autowired
    private IApCityRepository cityRepository;
    @Autowired
    private IApPatientSuggestionRepository patientSuggestionRepository;
    @Autowired
    private IApDepartmentRepository departmentRepository;
    @Autowired
    private IPatientRecordService patientRecordService;
    @Autowired
    private IPatientPersonDoctorDao patientPersonDoctorDao;
    @Autowired
    private IPatientFamilyService patientFamilyService;
    @Autowired
    private IPatientWeixinLogService weixinLogService;
    @Autowired
    private ICustomMsgApiService customMsgApiService;
    @Autowired
    private IWebAuthorizeApiService webAuthorizeApiService;
    @Autowired
    private IPatientDoctorService patientDoctorService;
    @Autowired
    private ITemplateNoticeService templateNoticeService;
    @Autowired
    private IPatientInquirerService inquirerService;
    @Autowired
    private IPatientInquirerRepository inquirerRepository;
    @Autowired
    private ISubscribeNoticeService subscribeNoticeService;
    @Autowired
    private ApMessageDao messageDao;
    @Autowired
    private EmrFeginClient emrFeginClient;
    @Autowired
    private IApConsultSessionRepository consultSessionRepository;
    @Autowired
    private IApPatientRepository patientRepository;
    @Autowired
    private IApDoctorChargeRepository doctorChargeRepository;
    @Autowired
    private IMessageSendService messageSendService;
    @Autowired
    private IPatientInquirerRepository patientInquirerRepository;
    @Autowired
    private ImFeginClient imFeginClient;
    @Autowired
    private IPatientInquirerService patientInquirerService;

    @Override
    public PatientVO getPatientBaseInfo(Long id) throws PatientBusinessException {
        Patient patient = patientDao.get(id);
        LOGGER.info("====patient：" + JSONUtil.toJsonStr(patient));
        // 患者不存在
        if (patient == null) {
            LOGGER.error("getPatientBaseInfo Error ; patient not exist : patientId {}", id);
            throw new PatientBusinessException(ExceptionCodes.PATIENT_NOT_EXIST);
        }
        // 患者账号被禁用
        if (patient.getAccountStatus() != 0) {
            LOGGER.error("getPatientBaseInfo Error ; patient account status wrong : patientId {}", id);
            throw new PatientBusinessException(ExceptionCodes.PATIENT_ACCOUNT_ERROR);
        }
        return buildBaseInfo(patient);
    }

    @Override
    @Transactional
    public PatientVO login(String openId) throws BusinessException {
        // 写库查询患者信息
        Patient patient = patientDao.getPatientByOpenId(openId);

        PatientVO patientVo;
        // 患者不存在
        if (patient == null) {
            LOGGER.error("login Error ; patient not exist : openId {}", openId);
            throw new PatientBusinessException(ExceptionCodes.PATIENT_NOT_EXIST);
        }
        // 患者账号被禁用
        if (patient.getAccountStatus() != 0) {
            LOGGER.error("login Error ; patient account status wrong : openId {}", openId);
            throw new PatientBusinessException(ExceptionCodes.PATIENT_ACCOUNT_ERROR);
        }
        patientVo = new PatientVO();
        patientVo.setPatientId(patient.getId());
        patientVo.setToken(DESPlus.gengerateLoginToken(patient.getId(), patient.getUnionId()));
        patientVo.setUnionId(patient.getUnionId());
        return patientVo;
    }

    @Override
    @Transactional(rollbackFor = Exception.class)
    public PatientVO saveBaseInfo(Long patientId, PatientBaseParam baseParam) throws PatientBusinessException {
        //  写库获取基础信息，后续会更新
        Patient patient = patientDao.get(patientId);
        // 患者不存在
        if (patient == null) {
            LOGGER.error("saveBaseInfo Error ;patient not exist : patientId {}", patientId);
            throw new PatientBusinessException(ExceptionCodes.PATIENT_NOT_EXIST);
        }
        // 患者账号被禁用
        if (patient.getAccountStatus() != 0) {
            LOGGER.error("saveBaseInfo Error ;patient account status wrong : patientId {}", patientId);
            throw new PatientBusinessException(ExceptionCodes.PATIENT_ACCOUNT_ERROR);
        }

        // 更新
        // 名字非空且不等于原名
        // 调整非空判断，增加非空字符串校验 Change By Gaoll - 2015-9-24 16:49:50
        // 表情过滤name = ""
        // baseParam.setName(NameUtils.handleUtf8mb4(baseParam.getName(), ""));
        if (null != baseParam.getName() && "".equals(baseParam.getName().trim())) {
            LOGGER.error("saveBaseInfo Error ; patient name is empty : patientId {}", patientId);
            throw new PatientBusinessException(ExceptionCodes.PATIENT_NAME_IS_NULL);
        }
        //校验姓名合法
        if (StringUtils.isNotBlank(baseParam.getName()) && !StringUtil.valRealName(baseParam.getName())) {
            throw new PatientBusinessException(ExceptionCodes.PATIENT_NAME_NOT_RULE);
        }
        if (!StringUtils.isEmpty(baseParam.getName()) && !baseParam.getName().equals(patient.getName())) {
            patient.setName(baseParam.getName().trim());
        }
        // 年龄非空切不等于原值
        if (null != baseParam.getAge() && (null == patient.getAge() || baseParam.getAge().compareTo(patient.getAge()) != 0)) {
            patient.setAge(baseParam.getAge());
        }

        // 城市非空且不等于原城市id
        if (null != baseParam.getCityId() && (null == patient.getCityId() || baseParam.getCityId().compareTo(patient.getCityId()) != 0)) {
            patient.setCityId(baseParam.getCityId());
        }
        // 身份证非空
        if (StringUtils.isNotEmpty(baseParam.getIdCard())) {
            String idCard = baseParam.getIdCard();
            // 长度规则校验(这里只限制最长18位的限制，防止随意输入进行入库)
            if (idCard.length() > 18) {
                throw new PatientBusinessException(ExceptionCodes.PATIENT_ID_CARD_NOT_RULE);
            }
            // 末尾为*的替换成X
            if (idCard.length() == 18 && idCard.toUpperCase().endsWith("*")) {
                idCard = idCard.substring(0, 17) + "X";
            }
            // 合法校验
            if (!IdcardUtil.isValidCard(idCard)) {
                throw new PatientBusinessException(ExceptionCodes.PATIENT_ID_CARD_NOT_RULE);
            }
            patient.setIdCard(baseParam.getIdCard());
            // 身份证号、手机号都有值则认证通过
            patient.setStatus(!StringUtils.isEmpty(patient.getPhone()) ? Patient.REAL_STATUS_SUCCESS : patient.getStatus());
            //根据证件号获取出生日期，性别
            getBirthAndGender(patient);
        }
        String birthday = null != patient.getBirthday() ? DateUtil.parseDateToString(patient.getBirthday(), DateUtil.DATE_TIME_FORMAT) : "";
        // 出生日期非空且不等于原有出生日期
        if (!StringUtils.isEmpty(baseParam.getBirthday()) && (null == patient.getBirthday() || !baseParam.getBirthday().equals(birthday))) {
            patient.setBirthday(DateUtil.parseStrToDateTime(baseParam.getBirthday()));
        }
        // 性别不为空且不等于原值
        if (null != baseParam.getGender() && (null == patient.getGender() || baseParam.getGender().compareTo(patient.getGender()) != 0)) {
            patient.setGender(baseParam.getGender());
        }

        // 婚姻状态
        if (null != baseParam.getMaritalStatus()) {
            patient.setMaritalStatus(baseParam.getMaritalStatus());
        }

        // 更新患者信息
        patientDao.saveOrUpdatePatient(patient);
        // 更新家庭成员本人信息
        patientFamilyService.insertOrUpdateSelf(patient);
        //更新就诊人本人信息
        inquirerService.insertOrUpdateSelf(patient, baseParam);

        return buildBaseInfo(patient);
    }

    /**
     * @param patient 患者信息
     *
     * @return
     *
     * @throws
     * @method
     * @author chengqiang
     * @description 根据证件号获取出生日期，性别
     * @date 2018/11/26 15:04
     **/
    private void getBirthAndGender(Patient patient) {

        try {
            String birthStr = IdcardUtil.getBirth(patient.getIdCard());
            patient.setBirthday(DateUtil.parseStrToCustomPatternDate(birthStr, DateUtil.DAY_TIME_FORMAT));
            patient.setGender(IdcardUtil.getGenderByIdCard(patient.getIdCard()));
        } catch (Exception e) {
            LOGGER.error("根据证件号获取出生日期，性别异常，证件号：{}", patient.getIdCard(), e);
        }
    }

    @Override
    @Transactional(rollbackFor = Exception.class)
    public PatientVO savePhone(Long patientId, String phoneNum) throws PatientBusinessException {
        //  写库获取基础信息，后续会更新
        Patient patient = patientDao.get(patientId);
        // 患者不存在
        if (patient == null) {
            LOGGER.error("savePhone Error ; patient not exist : patientId {}", patientId);
            throw new PatientBusinessException(ExceptionCodes.PATIENT_NOT_EXIST);
        }
        // 患者账号被禁用
        if (patient.getAccountStatus() != 0) {
            LOGGER.error("savePhone Error ; patient account status wrong : patientId {}", patientId);
            throw new PatientBusinessException(ExceptionCodes.PATIENT_ACCOUNT_ERROR);
        }
        // 如果手机号已经存在，不可以保存。手机号唯一
        Patient exist = patientDao.getByPhone(phoneNum);
        if (exist != null) {
            LOGGER.error("savePhone Error ; patient phoneNum in used : patientId {},phoneNum {}", patientId, phoneNum);
            throw new PatientBusinessException(ExceptionCodes.PATIENT_PHONE_EXIST);
        }
        // 是否首次绑定手机号
        boolean first = StringUtils.isBlank(patient.getPhone());
        patient.setPhone(phoneNum);
        // 身份证号、手机号都有值则认证通过
        patient.setStatus(!StringUtils.isEmpty(patient.getIdCard()) ? Patient.REAL_STATUS_SUCCESS : patient.getStatus());
        patientDao.save(patient);
        if (first) {
            // 首次激活处理推广医生积分
            addNewPatientPoint(patientId);
            // 清除新患者缓存，包括全部新患者集合中得key
            PatientCacheUtil.removeNewPatientInfoFromRedis(patientId);
            // 首次绑定 异步通知b2c更新
            //this.asynchronousService.synchronousPhoneToB2c(patient.getUnionId(), phoneNum);
        }

        // 异常情况不能影响手机号的绑定
        try {
            // 更新患者手机号相关手机联系人以及诊疗记录同步处理
            patientRecordService.updatePatientRecord(patient);
            // 更新家庭成员本人信息
            patientFamilyService.insertOrUpdateSelf(patient);
        } catch (Exception e) {
            // 手机联系人诊疗记录更新失败不影响绑定手机号，记录日志
            LOGGER.error("update patient record faild patientId {}, phone {}", patientId, phoneNum, e);
        }

        PatientVO patientVO = buildBaseInfo(patient);
        patientVO.setFirstBind(first);
        patientVO.setToken(patient.getToken());

        return patientVO;
    }

    /**
     * 构造患者基础信息返回
     *
     * @param patient 患者实体
     *
     * @return PatientVo <基础信息返回>
     *
     * @History 2015年6月24日 下午8:27:53 by Amber
     */
    private PatientVO buildBaseInfo(Patient patient) {
        List<PatientInquirerInfo> inquirerInfos = inquirerRepository
                .findByPatientIdAndRelation(patient.getId(), InquirerRelationEnum.ONESELF.getValue());
        PatientInquirerInfo inquirerInfo = null;
        if(CollectionUtils.isNotEmpty(inquirerInfos)){
            inquirerInfo = inquirerInfos.get(0);
        } else {
            inquirerInfo = new PatientInquirerInfo();
        }

        PatientVO patientVo = new PatientVO();
        patientVo.setPatientId(patient.getId());
        patientVo.setPhoto(patient.getHeadUrl() == null ? "" : patient.getHeadUrl());
        patientVo.setName(StringUtils.isEmpty(patient.getName()) ? patient.getNickName() : patient.getName());
        patientVo.setAge(patient.getAge() == null ? "" : patient.getAge().toString());
        patientVo.setGender(patient.getGender());
        patientVo.setPhone(patient.getPhone());
        patientVo.setCityId(inquirerInfo.getCityId());
        patientVo.setIdNo(patient.getIdCard());
        patientVo.setBirthday(null != patient.getBirthday() ? patient.getBirthday().getTime() : null);
        patientVo.setOpenId(patient.getOpenId());
        patientVo.setUnionId(patient.getUnionId());
        patientVo.setRealStatus(patient.getStatus());
        //0未填写，1已填写
        patientVo.setPhoneStatus(StringUtils.isBlank(patient.getPhone()) ? Patient.REAL_STATUS_UNAUDIT : Patient.REAL_STATUS_SUCCESS);
        patientVo.setStatus(patient.getStatus());
        patientVo.setContactName(inquirerInfo.getContactName());
        patientVo.setContactPhone(inquirerInfo.getContactPhone());
        patientVo.setMaritalStatus(null != inquirerInfo.getMaritalStatus() ? inquirerInfo.getMaritalStatus() : 0);
        patientVo.setAddress(inquirerInfo.getAddress());
        patientVo.setMiniOpenId(patient.getMiniOpenId());
        patientVo.setRealPhone(patient.getPhone());
        return patientVo;
    }

    @Override
    public PatientVO getSimpleInfo(Long patientId, String unionKey) throws PatientBusinessException {
        Patient patient = patientDao.get(patientId);
        // 患者不存在
        if (patient == null) {
            LOGGER.error("getSimpleInfo Error ; patient not exist : patientId {}", patientId);
            throw new PatientBusinessException(ExceptionCodes.PATIENT_NOT_EXIST);
        }
        // 患者账号被禁用
        if (patient.getAccountStatus() != 0) {
            LOGGER.error("getSimpleInfo Error ; patient account status wrong : patientId {}", patientId);
            throw new PatientBusinessException(ExceptionCodes.PATIENT_ACCOUNT_ERROR);
        }

        PatientVO simple = new PatientVO();
        simple.setPatientId(patient.getId());
        simple.setCanTalk(patient.cantalk());
        simple.setName(StringUtils.isEmpty(patient.getName()) ? patient.getNickName() : patient.getName());
        simple.setOpenId(patient.getOpenId());
        simple.setPhoto(patient.getHeadUrl() == null ? "" : patient.getHeadUrl());
        // 缓存没有时取数据库数据
        List<BigInteger> ids = patientDao.getPersonalIds(patientId);
        Integer personalNum = ids.size();
        if (ids.size() == 1) {
            // 只有一个私人医生时，设置医生id
            simple.setDefautDr(ids.get(0).toString());
        }
        simple.setPersonalNum(personalNum.toString());
        simple.setRealStatus(patient.getStatus());
        return simple;
    }

    @Override
    @Transactional(rollbackFor = Exception.class)
    public void saveSuggest(Long patientId, String suggestion, String phone) throws PatientBusinessException {
        Patient patient = patientDao.get(patientId);

        // 患者不存在
        if (patient == null) {
            LOGGER.error("saveSuggest Error ; patient not exist : patientId {}", patientId);
            throw new PatientBusinessException(ExceptionCodes.PATIENT_NOT_EXIST);
        }
        // 患者账号被禁用
        if (patient.getAccountStatus() != 0) {
            LOGGER.error("saveSuggest Error ; patient account status wrong : patientId {}", patientId);
            throw new PatientBusinessException(ExceptionCodes.PATIENT_ACCOUNT_ERROR);
        }
        // TODO: 是否需要过滤
        PatientSuggestion patientSuggestion = new PatientSuggestion();
        patientSuggestion.setName(patient.getName() == null ? patient.getNickName() : patient.getName());
        patientSuggestion.setUserId(patientId);
        patientSuggestion.setType(0);
        patientSuggestion.setPhone(phone);// 手机号
        patientSuggestion.setContent(suggestion);
        patientSuggestionRepository.save(patientSuggestion);
    }

    @Override
    @Transactional(rollbackFor = Exception.class)
    public void addUser(String unionId, Long userId) throws PatientBusinessException {
        // 写库获取基础信息，与后续更新确保在同一个session
        Patient patient = patientDao.getPatientByUnionId(unionId);
        if (patient == null || patient.getAccountStatus() != 0) {
            // 用户数据不存在，或已被禁用
            LOGGER.error("add User Error ; patient not found or useless : unionId {}", unionId);
            throw new PatientBusinessException(ExceptionCodes.PATIENT_ERROR);
        }
        patientDao.saveOrUpdatePatient(patient);
    }

    @Override
    @Transactional(rollbackFor = Exception.class)
    public void addNewPatientPoint(Long patientId) {

    }

    @Override
    public boolean unionValidate(String phoneNum) {
        Patient patient = patientDao.getByPhone(phoneNum);
        if (patient == null) {
            return true;
        }
        return false;
    }

    @Override
    public PatientVO getPersonalInfo(String openId) throws PatientBusinessException {
        // 读库查询患者基础信息
        Patient patient = patientDao.getPatientByOpenId(openId);
        // 患者不存在
        if (patient == null) {
            LOGGER.error("getPersonalInfo Error ; patient not exist : openId {}", openId);
            throw new PatientBusinessException(ExceptionCodes.PATIENT_NOT_EXIST);
        }
        // 患者账户不存在或状态异常
        if (patient.getAccountStatus() != 0) {
            LOGGER.error("getPersonalInfo Error ; patient account status wrong : openId {}", openId);
            throw new PatientBusinessException(ExceptionCodes.PATIENT_ACCOUNT_ERROR);
        }
        PatientVO patientVo = new PatientVO();
        patientVo.setPatientId(patient.getId());
        // 查询患者有效私人医生数量，有绑定关系（含禁用状态医生），
        List<BigInteger> ids = patientDao.getPersonalIds(patient.getId(), true);
        if (ids.size() > 0) {
            patientVo.setHavePersonal(true);
        } else {
            patientVo.setHavePersonal(false);
        }
        return patientVo;
    }

    @Override
    public PatientVO getPatientInfoForB2c(String unionId) throws PatientBusinessException {
        // 用于买药回调，从读库读取患者信息
        Patient patient = patientDao.getPatientByUnionId(unionId);
        if (patient == null) {
            LOGGER.error("getPatientInfoForB2c Error ; patient not exist : unionId {}", unionId);
            throw new PatientBusinessException(ExceptionCodes.PATIENT_NOT_EXIST);
        }
        if (patient.getAccountStatus() != 0) {
            LOGGER.error("getPatientInfoForB2c Error ; patient account status wrong : unionId {}", unionId);
            throw new PatientBusinessException(ExceptionCodes.PATIENT_ACCOUNT_ERROR);
        }
        // 初始化患者信息
        PatientVO patientVo = new PatientVO();
        patientVo.setPatientId(patient.getId());
        patientVo.setName(StringUtils.isEmpty(patient.getName()) ? patient.getNickName() : patient.getName());
        patientVo.setAge(patient.getAge() == null ? "" : patient.getAge().toString());
        patientVo.setGender(patient.getGender());
        patientVo.setPhone(patient.getPhone() == null ? "" : patient.getPhone());
        // 如果患者有推广医生
        if (patient.getRecomDoctor() != null) {
            // 读库查询医生信息
            Doctor doctor = doctorDao.get(patient.getRecomDoctor());
            if (doctor == null || doctor.getAccountStatus() != 0) {
                // 医生账户异常
                LOGGER.error("getPatientInfoForB2c Error ; doctor not exist or account error : patientId {} doctorId {}", patient.getId(),
                             patient.getRecomDoctor());
            } else if (doctor.getDepartmentId() != null && doctor.getDepartmentId() != 0) {// 存在科室，且非自定义
                // 医生有科室，先从缓存查询科室信息
                DepartmentVO departmentVO = BaseParamCacheUtil.getDepartmentFromRedis(doctor.getDepartmentId());
                if (departmentVO != null) {
                    // 设置患者科室
                    patientVo.setDepartment(departmentVO.getName());
                } else {
                    // 缓存中没有则去读库查询
                    Department department = departmentRepository.get(doctor.getDepartmentId());
                    if (department != null) {
                        // 设置患者科室
                        patientVo.setDepartment(department.getName());
                    }
                }
            } else {
                patientVo.setDepartment(doctor.getCustomDepartment() == null ? "" : doctor.getCustomDepartment());
            }
        }
        return patientVo;
    }

    @Override
    @Transactional(rollbackFor = Exception.class)
    public void saveRegisterInfo(RegisterParamBO register) throws BusinessException {
        LOGGER.info("注册参数:{}", register);

        // 处理微信性别信息与代码解析不一致问题
        if (null != register.getGender()) {
            RecomPatientBO.GenderEnum genderEnum = RecomPatientBO.GenderEnum.getByWeixinId(register.getGender());
            if (null != genderEnum) {
                register.setGender(genderEnum.getId());
            } else {
                register.setGender(RecomPatientBO.GenderEnum.GENDER_UNKNOWN.getId());
            }
        }

        if (register == null || register.getOpenId() == null) {
            LOGGER.warn("注册参数异常 {}", register);
            throw new BusinessException(ExceptionCodes.PARAM_ERROR);
        }

        // 写库查询是否已有患者用户
        String openId = register.getOpenId();
        //Patient patient = patientDao.getPatientByOpenId(openId);
        Patient patient = patientDao.getPatientByUnionId(register.getUnionId());

        if (patient != null) {
            // 账户异常，请联系客服
            if (patient.getAccountStatus() != 0) {
                LOGGER.error("register Error ; patient account status wrong : openId {}", openId);
                PatientBusinessException patientBusinessException = new PatientBusinessException(ExceptionCodes.PATIENT_ACCOUNT_ERROR);
                customMsgApiService.sendSystemMsg(openId, patientBusinessException.getMessage());
                throw patientBusinessException;
            }
            patient.setNewUser(false);

            // 重新关注
            patient.setCancel(PatientConstant.CANCEL_STATUS_NO);
            if (patient.getCancel() == PatientConstant.CANCEL_STATUS_YES) {
                customMsgApiService.sendSystemMsg(openId, ConfigKeyUtil.getString("wachat.follow.ehp.message"));

                // 患者未完善资料并且自主注册 发送通知
                /*if (patient.getStatus() == Patient.REAL_STATUS_UNAUDIT && RegisterParamBO.ME_REG_SOURCE == register.getRegisterSource()) {
                    String authUrl = webAuthorizeApiService.getWebAuthorizeUrlSilent(ConfigKeyUtil.getString("wechat.redirect.common.url"),
                        PatientJumperEnum.PATIENT_AUTH.encodeWxParamStr(),
                        ConfigKeyUtil.getString("wachat.redirect.info"));
                    customMsgApiService.sendSystemMsg(openId, ConfigKeyUtil.getString("wachat.again.follow.message") + authUrl);
                }*/

            }
            if (StringUtils.isNotEmpty(openId)) {
                patient.setOpenId(openId);
            }
            if (StringUtils.isNotEmpty(patient.getUnionId())) {
                // 患者用户是根据注册小程序自主注册，更新信息
                if (register.getGender() != null) {
                    patient.setGender(register.getGender());
                }
                patient.setNewUser(true);
                patient.setRegistSource(PatientConstant.REGIST_SOURCE_SELF);
                // 匹配城市信息
                updateCityInfo(patient, register.getCity(), register.getProvince());
                patient.setChangedAt(new Date());
            }
            if (StringUtils.isBlank(patient.getNickName())) {
                patient.setNickName(ConfigUtil.getString("pt.default.name"));
            }
            if (StringUtils.isBlank(patient.getHeadUrl())) {
                patient.setHeadUrl(ConfigUtil.getString("pt.default.head"));
            }
            patientDao.update(patient);
            List<PatientInquirerInfo> patientInquirerInfos = patientInquirerRepository
                .findByPatientIdAndRelation(patient.getId(), InquirerRelationEnum.ONESELF.getValue());
            if (CollectionUtils.isEmpty(patientInquirerInfos)) {
                // 判断没有就诊人本人信息就增加就诊人本人信息
                PatientInquirerInfo patientInquirerInfo = PatientInquirerInfo.builder()
                    .patientId(patient.getId())
                    .relation(InquirerRelationEnum.ONESELF.getValue())
                    .name(null != patient.getName() ? patient.getName() : patient.getNickName())
                    .status(PatientInquirerInfo.STATUS_DEFAULT)
                    .childTag(PatientInquirerInfo.CHILD_TAG_NO)
                    .createdAt(new Date())
                    .build();
                patientInquirerRepository.save(patientInquirerInfo);
            }
            return;
        }

        patient = new Patient();
        BeanUtils.copyProperties(register, patient);
        if (StringUtils.isBlank(patient.getNickName())) {
            patient.setNickName(ConfigUtil.getString("pt.default.name"));
        }
        if (StringUtils.isBlank(patient.getHeadUrl())) {
            patient.setHeadUrl(ConfigUtil.getString("pt.default.head"));
        }
        // 默认姓名采用昵称
        patient.setName(register.getNickName());
        if (StringUtils.isBlank(patient.getUnionId())) {
            patient.setUnionId(patient.getOpenId());
        }
        patient.setRegistSource(register.getRegisterSource());
        if (StringUtils.isBlank(patient.getUnionId())) {
            patient.setUnionId(patient.getOpenId());
        }

        patient.setNewUser(true);
        patient.setRegistSource(PatientConstant.REGIST_SOURCE_SELF);
        // 匹配城市信息
        updateCityInfo(patient, register.getCity(), register.getProvince());
        Date nowTime = new Date();
        patient.setCreateAt(nowTime);
        patient.setCancel(PatientConstant.CANCEL_STATUS_NO);
        Patient addPatient = patientDao.save(patient);
        // 增加就诊人本人信息
        Long patientId = addPatient.getId();
        List<PatientInquirerInfo> patientInquirerInfos = patientInquirerRepository
            .findByPatientIdAndRelation(patientId, InquirerRelationEnum.ONESELF.getValue());
        if (CollectionUtils.isEmpty(patientInquirerInfos)) {
            // 判断没有就诊人本人信息就增加就诊人本人信息
            PatientInquirerInfo patientInquirerInfo = PatientInquirerInfo.builder()
                .patientId(patientId)
                .relation(InquirerRelationEnum.ONESELF.getValue())
                .name(addPatient.getNickName())
                .gender(addPatient.getGender())
                .phone(addPatient.getPhone())
                .status(PatientInquirerInfo.STATUS_DEFAULT)
                .childTag(PatientInquirerInfo.CHILD_TAG_NO)
                .accountStatus(PatientInquirerInfo.ACCOUNT_STATUS_NO)
                .createdAt(new Date())
                .build();
            patientInquirerRepository.save(patientInquirerInfo);
        }

        // 新用户放入缓存
        setPatientInfoToCache(patient.getId(), openId, nowTime);
        // 发送通知
        customMsgApiService.sendSystemMsg(openId, ConfigKeyUtil.getString("wachat.follow.ehp.message"));
    }

    @Override
    @Transactional(rollbackFor = Exception.class)
    public void saveBindInfo(String openId, Long doctorId) {
        LOGGER.info("医患关系绑定 openId:{} doctorId:{}", openId, doctorId);
        Patient patient = patientDao.getPatientByOpenId(openId);
        try {
            bindDoctor(openId, doctorId, patient);
        } catch (BusinessException e) {
            LOGGER.error("患者绑定医生异常 ", e);
            customMsgApiService.sendSystemMsg(openId, e.getMessage());
        }
    }

    @Override
    @Transactional(rollbackFor = Exception.class)
    public String updateCode2token(String code) throws PatientBusinessException {
        String openId = RedisUtil.valueOps().getString(code);
        RedisUtil.keyOps().delete(code);

        Patient patient = patientDao.getPatientByOpenId(openId);
        if (patient == null || patient.getAccountStatus() != 0) {
            throw new PatientBusinessException(ExceptionCodes.TOLOGIN);
        }
        String token = JwtHelper.genToken(JwtUser
                                                  .builder()
                                                  .userId(patient.getId())
                                                  .userType(JwtUser.USER_TYPE_PATIENT)
                                                  .build());
        patient.setToken(token);
        patientDao.update(patient);

        String cacheKey = PatientCacheUtil.PATIENT_LOGIN_TOKEN_KEY + patient.getId();
        RedisUtil.valueOps().set(cacheKey, token);

        return token;
    }

    /**
     * 医患绑定
     *
     * @param openId   openId
     * @param doctorId 医生ID
     * @param patient  患者INFO
     *
     * @throws BusinessException exception
     */
    private void bindDoctor(String openId, Long doctorId, Patient patient) throws BusinessException {
        if (patient == null) {
            throw new PatientBusinessException(ExceptionCodes.PATIENT_NOT_EXIST);
        }

        // 账户异常，请联系客服
        if (patient.getAccountStatus() != 0) {
            LOGGER.error("register Error ; patient account status wrong : openId {}", openId);
            throw new PatientBusinessException(ExceptionCodes.PATIENT_ACCOUNT_ERROR);
        }

        Long patientId = patient.getId();
        Doctor doctor = doctorDao.get(doctorId);

        // 医生信息校验
        if (doctor == null || doctor.getId() == null) {
            LOGGER.error("该医生不存在 patientId {}, doctorId {}", patientId, doctorId);
            throw new PatientBusinessException(ExceptionCodes.DOCTOR_NOT_EXIST);
        }
        // 医生账号被禁用
        if (doctor.getAccountStatus() != 0) {
            LOGGER.error("addPersonalDoctor Error ; doctor account status wrong : patientId {}, doctorId {}", patientId, doctorId);
            throw new PatientBusinessException(ExceptionCodes.DOCTOR_ACCOUNT_ERROR);
        }

        // 是否已存在关联关系
        if (patientDao.existPersonalRelation(patientId, doctorId)) {
            sendMessage(doctor, patient, openId, false);
            return;
        }
        PatientPersonDoctor patientPersonDoctor = patientPersonDoctorDao.findByPatientIdAndDoctorId(patientId, doctorId);
        if (patientPersonDoctor != null) {
            // 已存在绑定关系，再次扫码
            if (patientPersonDoctor.getRelationFlag() == PatientPersonDoctor.RELATION_FLAG_DELETE) {
                LOGGER.debug("医患关系已经删除 doctorId:{} patientId:{}", doctorId, patientId);
                throw new BusinessException(ExceptionCodes.DOCTOR_ACCOUNT_DISABLE);
            } else {
                // 使用ResourceHolder回传患者id
                log.info("资源持有器线程上下文-存入数据-医患绑定异常，patientId {}", patientId);
                ResourceHolder.bindResource("patientId", patientId);
                // 已经绑定，无需再绑定
                LOGGER.error("addPersonalDoctor Error ; have bound : patientId {}, doctorId {}", patientId, doctorId);
                // throw new PatientBusinessException(ExceptionCodes.HAVE_BOUND);
                sendMessage(doctor, patient, openId, false);
                return;
            }
        }

        // 绑定私人医生关系
        patientDao.addPersonalDoctor(doctorId, patientId, PatientConstant.PERSONAL_SOURCE_WEIXIN);
        // 发送通知
        sendMessage(doctor, patient, openId, true);

        // 医生端推送通知
        NoticeUtil.newPatientNotice(doctorId, patientId);

        // 向im发送消息
        // sendImSysMessage(doctorId, patientId);

//        // 开启免费会话
//        PatientInquirerInfo patientInquirerInfo = patientInquirerRepository.findFirstByPatientIdAndRelation(patientId, 0);
//        messageSendService.sendFirstFreeSession(patientId, patientInquirerInfo.getId(), doctorId, patient.getOpenId(), doctor.getName());

        // 绑定私人医生后，设置医生图文咨询费
        //this.remoter.addDoctorDefaultMessageCharge(patientId, doctorId);

    }

    /**
     * 医患绑定消息发送
     */
    private void sendMessage(Doctor doctor, Patient patient, String openId, Boolean isFirst) {
        // 设置医院名称
        String hospitalName;
        if ((doctor.getHospitalId() != null) && (0 != doctor.getHospitalId())) {
            hospitalName = doctorDao.getHospitalName(doctor.getHospitalId());
        } else {
            hospitalName = doctor.getHospitalName();
        }

        // 线上医院统一使用互联网医院通用名
        hospitalName = ConfigUtil.getString("ehp.hospital.name");

        // 医生所在科室
        Integer departmentId = doctor.getDepartmentId();
        if (departmentId != null && departmentId != 0) {
            // 医生有科室，先从缓存查询科室信息
            DepartmentVO departmentVO = BaseParamCacheUtil.getDepartmentFromRedis(departmentId);
            if (departmentVO != null) {
                // 设置患者科室
                hospitalName += departmentVO.getName();
            } else {
                // 缓存中没有则去读库查询
                Department department = departmentRepository.get(departmentId);
                if (department != null) {
                    // 设置患者科室
                    hospitalName += department.getName();
                }
            }
        } else {
            hospitalName += doctor.getCustomDepartment() == null ? "" : doctor.getCustomDepartment();
        }

//        String format;

        if (Patient.REAL_STATUS_UNAUDIT == patient.getStatus()) {
            String title = String.format(ConfigKeyUtil.getString("wachat.follow.info.message"), hospitalName,
                doctor.getName());
            String pagePath = "/pages/famousDoctorDetail/famousDoctorDetail?type=3&doctorId=" + doctor.getId();
            String msg = title + "<a href='http://www.qq.com' data-miniprogram-appid='"
                + wxMiniappAppid
                + "' data-miniprogram-path='"
                + pagePath
                + "'>查看医生详情</a>";
            customMsgApiService.sendSystemMsg(patient.getOpenId(), msg);
            //customMsgApiService.sendMiniProgramMsg(patient.getOpenId(), title, wxMiniappAppid, pagePath, null);
        } else {
          /*  format = String.format(ConfigKeyUtil.getString("wachat.sysmsg.disclaimer"), doctor.getName(),
                webAuthorizeApiService.getWebAuthorizeUrlSilent(ConfigUtil.getString("wechat.redirect.common.url"),
                    PatientJumperEnum.PATIENT_MEDICAL_RECORD_LIST.encodeWxParamStr(
                        doctor.getId().toString()), "点击选择就诊人或者添加就诊人"));*/
        }
        // String clinicMsg = null;
        String title = null;
        String pagePath = null;
        if (null != isFirst && isFirst) {
            // DoctorCharge doctorCharge = doctorChargeRepository.findFirstByDoctorIdAndChargeType(doctor.getId(),
            // DoctorCharge.CHARGE_TYPE_INQUIRING);
            // if (null != doctorCharge && doctorCharge.getCharge() > 0) {
            // // 收费
            // title = String.format(ConfigKeyUtil.getString("wachat.follow.info.first.binddoctor.paid.message"),
            // hospitalName,
            // doctor.getName(), doctorCharge.getCharge() / 100);
            // pagePath = "/pages/consult/chat/chat?doctorId=" + doctor.getId();
            // } else {
            // // 免费
            // title = String.format(ConfigKeyUtil.getString("wachat.follow.info.first.binddoctor.free.message"),
            // hospitalName,
            // doctor.getName());
            // pagePath = "/pages/consult/chat/chat?doctorId=" + doctor.getId();
            // }
        } else {
            // 非首次

            title = String.format(ConfigKeyUtil.getString("wachat.follow.info.second.binddoctor.message"), hospitalName,
                doctor.getName());
            pagePath = "/pages/consult/chat/chat?doctorId=" + doctor.getId();
        }

        if (StringUtils.isNotEmpty(title)) {
            String buttonName = ConfigUtil.getString("wachat.clinic.redirect.info");
            String msg = title + "<a href='http://www.qq.com' data-miniprogram-appid='"
                + wxMiniappAppid
                + "' data-miniprogram-path='"
                + pagePath
                + "'>" + buttonName + "</a>";
            customMsgApiService.sendSystemMsg(patient.getOpenId(), msg);
            // customMsgApiService.sendMiniProgramMsg(patient.getOpenId(), title, wxMiniappAppid, pagePath, null);
        }

        // 设置当前咨询医生
        patientDoctorService.startChat(patient, doctor, false);
    }

    /**
     * 添加新患者信息到缓存 PatientController.setPatientInfoToCache()<BR> <P>Author :
     * Amber </P> <P>Date : 2015年9月16日 </P>
     *
     * @param patientId 患者id
     * @param openId    患者openId
     * @param registAt  注册时间
     */
    private void setPatientInfoToCache(Long patientId, String openId, Date registAt) {
        try {
            // 参数校验
            if (patientId == null || StringUtils.isBlank(openId)) {
                LOGGER.error("setPatientInfoToCache ERROR ; patientId or openId is null : patientId {},openId {}", patientId, openId);
                return;
            }
            Map<String, String> patientMap = new HashMap<>();
            patientMap.put("userid", patientId.toString());
            patientMap.put("openid", openId);
            patientMap.put("ctime", DateUtil.formatDate(registAt, DateUtil.DATE_FORMAT));
            // 缓存患者信息
            PatientCacheUtil.addNewPatientInfoToRedis(patientId, patientMap);
            // 追加新患者
            PatientCacheUtil.appendNewPatientsKeyToRedis(patientId);
        } catch (Exception e) {
            LOGGER.error("setPatientInfoToCache ERROR ; patientId {},openId {}", patientId, openId, e);
        }
    }

    /**
     * <根据省市名称处理患者城市信息>
     *
     * @param patient
     * @param cityName     市名
     * @param provinceName 省名
     *
     * @return void <返回值描述>
     *
     * @Throws 异常信息
     * @History 2015年11月5日 下午10:11:42 by Amber
     */
    private void updateCityInfo(Patient patient, String cityName, String provinceName) {
        // 省
        City provinceCity = null;
        // 市
        City city = null;

        // 查找省
        if (StringUtils.isNotEmpty(provinceName)) {
            provinceCity = cityRepository.findFirstByNameLikeAndType("%" + provinceName + "%", 1);
            // 省级为直辖市，查找下一级
            if (provinceCity != null && provinceCity.getMunicipality() == 1) {
                provinceCity = cityRepository.findFirstByNameLikeAndType("%" + provinceName + "%", 2);
            }
        }
        // 根据省查找市
        if (StringUtils.isNotEmpty(cityName)) {
            city = cityRepository.findFirstByNameLikeAndParentId("%" + cityName + "%", provinceCity == null ? null : provinceCity.getId());
        }

        // 如果有市
        if (city != null) {
            patient.setCityId(city.getId());
        } else
            // 设置省为患者城市信息
            if (provinceCity != null) {
                patient.setCityId(provinceCity.getId());
            }
    }

    @Override
    @Transactional(readOnly = false)
    public void cancelAttend(String openId) throws BusinessException {
        // 获取患者信息
        Patient patient = patientDao.getPatientByOpenId(openId);
        // 患者存在，且是未取消状态。
        if (patient != null && patient.getCancel() != PatientConstant.CANCEL_STATUS_YES) {
            patient.setCancel(PatientConstant.CANCEL_STATUS_YES);
            patient.setCancelAt(new Date());
            patientDao.saveOrUpdatePatient(patient);

            // 添加取消公众号日志
            weixinLogService.addCancelLog(patient.getId());
        }
    }

    @Override
    public PatientVO getOpenId(String unionId) throws BusinessException {
        // 获取患者信息
        Patient patient = patientDao.getPatientByUnionId(unionId);
        // 患者不存在
        if (patient == null) {
            LOGGER.error("getOpenId Error ; patient not exist : unionId {}", unionId);
            throw new PatientBusinessException(ExceptionCodes.PATIENT_NOT_EXIST);
        }
        // 患者不存在或账号被禁用
        if (patient.getAccountStatus() != 0) {
            LOGGER.error("getOpenId Error ; patient account status wrong : unionId {}", unionId);
            throw new BusinessException(ExceptionCodes.PATIENT_ACCOUNT_ERROR);
        }
        // 返回openId
        PatientVO patientVO = new PatientVO();
        patientVO.setPatientId(patient.getId());
        patientVO.setOpenId(patient.getOpenId());
        return patientVO;
    }

    @Override
    public Patient getPatient(Long patientId) {
        return patientDao.getPatient(patientId);
    }

    /**
     * 是否存在私有医生
     * IPatientService.getPersonalIds()
     *
     * @param patientId
     *
     * @return
     *
     * @Author YongYang
     * @Date 2016年7月21日
     * @since 1.0.0
     */
    @Override
    public List<BigInteger> getPersonalIds(Long patientId) {
        return patientDao.getPersonalIds(patientId);
    }

    @Override
    @Transactional
    public DeviceVerifyVO getVerifyInfo(Long patientId, String deviceSN) throws PatientBusinessException {
        DeviceVerifyVO verify;
        DeviceVO vo = (DeviceVO) RedisUtil.valueOps().getObject(PatientCacheUtil.getPatientLoginDeviceSnKey(patientId));
        if (vo == null) {
            Patient patient = patientDao.get(patientId);
            if (patient == null) {
                throw new PatientBusinessException(ExceptionCodes.PATIENT_NOT_EXIST);
            }
            vo = new DeviceVO(patient.getDeviceSN(), patient.getModel(), patient.getLoginAddr());
            RedisUtil.valueOps().set(PatientCacheUtil.getPatientLoginDeviceSnKey(patientId), vo);
        }
        if (deviceSN.equals(vo.getDeviceSN())) {
            verify = new DeviceVerifyVO(false, vo.getCreatedAt(), vo.getModel(), vo.getLoginAddr());
        } else {
            verify = new DeviceVerifyVO(true, vo.getCreatedAt(), vo.getModel(), vo.getLoginAddr());
        }
        return verify;
    }

    @Override
    public InnerPatientVO getPatientById(String unionId, Long patientId) {

        Patient patient = null;
        if (patientId != null) {
            patient = patientDao.get(patientId);
        } else {
            patient = patientDao.getPatientByUnionId(unionId);
        }

        InnerPatientVO patientDTO = new InnerPatientVO();
        if (patient != null) {
            patientDTO.setId(patient.getId());
            patientDTO.setOpenId(patient.getOpenId());
            patientDTO.setRecomDoctor(patient.getRecomDoctor());
        }
        return patientDTO;
    }

    @Override
    public void requireProcessNotice(RequireReplyTemplateMsgBO requireReplyTemplateMsgBO) {
        messageSendService.publishMaMessage(MsgMaPublishParamBO.builder()
            .doctorId(requireReplyTemplateMsgBO.getDoctorId())
            .patientId(requireReplyTemplateMsgBO.getPatientId())
            .msgType(MsgMaPublishParamBO.MsgType.CONTINUED_PRESCRIPTION)
            .path("/pages/recipeDetail/recipeDetail?recomId=" + requireReplyTemplateMsgBO.getRecomId() + "&type=2")
            .content("暂停用药，原因：" + requireReplyTemplateMsgBO.getProcessRemark())
            .build());
//        ApplyResultSubscribe resultSubscribe = ApplyResultSubscribe.builder()
//            .result(requireReplyTemplateMsgBO.getProcessStaus())
//            .time(requireReplyTemplateMsgBO.getProcessTime())
//            .describe(requireReplyTemplateMsgBO.getProcessRemark())
//            .build();
//        resultSubscribe.setOpenId(patient.getOpenId());
//        Integer errcode = subscribeNoticeService.notice(resultSubscribe);
//        if (errcode != 0) {
//            RequireReplyMsg msg = RequireReplyMsg.builder().build();
//            BeanUtil.copyProperties(requireReplyTemplateMsgBO, msg);
//            msg.setOpenId(patient.getOpenId());
//            templateNoticeService.notice(msg);
//        }
    }

    @Override
    public PatientBO getPatientInfo(String unionId, String miniOpenId, Long patientId) throws PatientBusinessException {
        Patient patient;
        LOGGER.info("getPatientInfo param unionId:{},miniOpenId:{},patientId:{}", unionId, miniOpenId, patientId);
        if (patientId != null) {
            patient = patientDao.get(patientId);
        } else if (StringUtils.isNotBlank(unionId)){
            patient = patientDao.getPatientByUnionId(unionId);
        } else {
            patient = patientDao.getPatientByMiniOpenId(miniOpenId);
        }
        if (patient == null) {
            return null;
        }
        PatientBO patientBO = new PatientBO();
        patientBO.setPatientId(patient.getId());
        BeanUtils.copyProperties(patient, patientBO);
        return patientBO;
    }

    @Override
    @Transactional(readOnly = false, rollbackFor = Exception.class)
    public Boolean bindMiniOpenId(String unionId, String miniAppId, String nickName, String avatar, Integer gender,
        String phone) throws PatientBusinessException {
        Patient patient = patientDao.getPatientByUnionId(unionId);
        boolean addTag = false;
        if (patient == null) {
            // throw new PatientBusinessException(ExceptionCodes.PATIENT_NOT_EXIST);
            // 患者记录不存在就注册一条默认患者用户
            patient = new Patient();
            patient.setMiniOpenId(miniAppId);
            patient.setUnionId(unionId);
            patient.setStatus(Patient.REAL_STATUS_UNAUDIT);
            patient.setCreateAt(new Date());
            patient.setLabelType(0);
            patient.setRegistSource(0);
            patient.setCancel(Patient.PATIENT_CALCEL_NO_FOCUS);
            patient.setType(Patient.PATIENT_TYPE_NORMAL);
            patient.setFirstCaseTag(0);
            patient.setNickName(nickName);
            patient.setName(nickName);
            patient.setHeadUrl(avatar);
            patient.setGender(gender);
            patient.setPhone(phone);
            addTag = true;
        } else {
            patient.setMiniOpenId(miniAppId);
            patient.setNickName(nickName);
            patient.setHeadUrl(avatar);
            if (StrUtil.isNotBlank(phone)) {
                patient.setPhone(phone);
            }
            PatientInquirerInfo patientInquirerInfo = patientInquirerRepository
                .findFirstByPatientIdAndRelation(patient.getId(), InquirerRelationEnum.ONESELF.getValue());
            // 就诊人信息为空
            if (null != patientInquirerInfo && StringUtils.isEmpty(patientInquirerInfo.getName())) {
                patientInquirerInfo.setName(nickName);
                patientInquirerRepository.update(patientInquirerInfo);
            }
            if (patientInquirerInfo == null) {
                patient.setGender(gender);
            }
        }
        // TODO 如果患者未关注公众号 openId 使用miniOpenId替换 当关注公众号的时候会覆盖会openId
        if (StringUtils.isBlank(patient.getOpenId())) {
            patient.setOpenId(miniAppId);
        }
        Patient savePatient = patientDao.saveOrUpdate(patient);

        if (addTag) {
            // 增加就诊人本人信息
            Long patientId = savePatient.getId();
            List<PatientInquirerInfo> patientInquirerInfos = patientInquirerRepository
                .findByPatientIdAndRelation(patientId, InquirerRelationEnum.ONESELF.getValue());
            if (CollectionUtils.isEmpty(patientInquirerInfos)) {
                // 判断没有就诊人本人信息就增加就诊人本人信息
                PatientInquirerInfo patientInquirerInfo = PatientInquirerInfo.builder()
                    .patientId(patientId)
                    .relation(InquirerRelationEnum.ONESELF.getValue())
                    .name(savePatient.getNickName())
                    .status(PatientInquirerInfo.STATUS_DEFAULT)
                    .childTag(PatientInquirerInfo.CHILD_TAG_NO)
                    .accountStatus(PatientInquirerInfo.ACCOUNT_STATUS_NO)
                    .createdAt(new Date())
                    .build();
                patientInquirerRepository.save(patientInquirerInfo);
            }
        }


        return true;
    }

    @Override
    public PatientBaseBO getPatientBaseInfoById(Long patientId) throws PatientBusinessException {
        Patient patient = patientDao.get(patientId);
        LOGGER.info("====patient：" + JSONUtil.toJsonStr(patient));
        // 患者不存在
        if (patient == null) {
            LOGGER.error("getPatientBaseInfo Error ; patient not exist : patientId {}", patientId);
            throw new PatientBusinessException(ExceptionCodes.PATIENT_NOT_EXIST);
        }
        // 患者账号被禁用
        if (patient.getAccountStatus() != 0) {
            LOGGER.error("getPatientBaseInfo Error ; patient account status wrong : patientId {}", patientId);
            throw new PatientBusinessException(ExceptionCodes.PATIENT_ACCOUNT_ERROR);
        }
        return PatientBaseBO.builder()
            .patientId(patient.getId())
            .phone(patient.getPhone())
            .name(StringUtils.isEmpty(patient.getName()) ? patient.getNickName() : patient.getName())
            .openId(patient.getOpenId())
            .unionId(patient.getUnionId())
            .gender(patient.getGender())
            .age(patient.getAge() == null ? "" : patient.getAge().toString())
            .build();
    }

    @Override
    public AddPatientRecordInitializeBO getAddRecordInitializerInfo(Long inquirerId, String sessionId) {
        AddPatientRecordInitializeBO result = new AddPatientRecordInitializeBO();
        try {
            List<com.naiterui.ehp.bs.patient.vo.im.Message> messages = messageDao.getPatientDisease(sessionId);
            ConsultSession consultSession = consultSessionRepository.findFirstBySessionId(sessionId);
            LOGGER.info("获取到会话病情消息记录数 {}", messages.size());
            com.naiterui.ehp.bs.patient.vo.im.Message message = null;

            if (CollectionUtil.isNotEmpty(messages)) {
                message = messages.get(0);
                LOGGER.info("获取到会话病情消息信息 {}", message.toString());
                Extend exd = message.getExd();
                // 就诊人ID
                inquirerId = message.getInquirerId();
                LOGGER.info("获取到会话病情消息信息就诊人ID {}", inquirerId);
                if (null != exd) {
                    // 病情ID
                    String diseaseId = exd.getDiseaseId();
                    LOGGER.info("获取到会话病情消息信息病情ID {}", exd.getDiseaseId());
                    PatientDiseaseBO patientDiseaseBO = emrFeginClient.getDisease(consultSession.getPatientId());
                    if (null != patientDiseaseBO) {
                        LOGGER.info("获取到会话病情消息信息病情信息 {}", patientDiseaseBO.toString());
                        result.setOfflineDiagnosis(patientDiseaseBO.getOfflineDiagnosis());
                    }
                }

                if (null != inquirerId) {
                    PatientInquirerInfo patientInquirerInfo = inquirerRepository.get(inquirerId);
                    result.setAllergy(patientInquirerInfo.getAllergy());
                    result.setPastHistory(patientInquirerInfo.getPastHistory());
                }
            } else if (null != inquirerId) {
                // sessionId为空，没有会话的情况
                PatientInquirerInfo patientInquirerInfo = patientInquirerRepository.get(inquirerId);
                Long patientId = patientInquirerInfo.getPatientId();
                PatientDiseaseBO patientDiseaseBO = emrFeginClient.getDisease(patientId);
                if (null != patientDiseaseBO) {
                    LOGGER.info("获取到会话病情消息信息病情信息 {}", patientDiseaseBO.toString());
                    result.setOfflineDiagnosis(patientDiseaseBO.getOfflineDiagnosis());
                }
                result.setAllergy(patientInquirerInfo.getAllergy());
                result.setPastHistory(patientInquirerInfo.getPastHistory());
            }
        } catch (Exception e) {
            LOGGER.error("获取添加病历回写数据异常， 异常信息 {}", e);
        }

        result.setAllergy(StringUtils.isNotEmpty(result.getAllergy()) ? result.getAllergy() : "无");
        result.setPastHistory(StringUtils.isNotEmpty(result.getPastHistory()) ? result.getPastHistory() : "无");

        return result;
    }

    @Override
    public void addPatientWithMiniapp(String miniOpenId, String unionId) {
        Patient patient = new Patient();
        patient.setMiniOpenId(miniOpenId);
        patient.setUnionId(unionId);
        patient.setStatus(Patient.REAL_STATUS_UNAUDIT);
        patient.setCreateAt(new Date());
        patient.setLabelType(0);
        patient.setRegistSource(0);
        patient.setCancel(0);
        patient.setType(Patient.PATIENT_TYPE_NORMAL);
        patient.setFirstCaseTag(0);
        Patient addPatient = patientRepository.save(patient);

        // 增加就诊人本人信息
        Long patientId = addPatient.getId();
        List<PatientInquirerInfo> patientInquirerInfos = patientInquirerRepository
            .findByPatientIdAndRelation(patientId, InquirerRelationEnum.ONESELF.getValue());
        if (CollectionUtils.isEmpty(patientInquirerInfos)) {
            // 判断没有就诊人本人信息就增加就诊人本人信息
            PatientInquirerInfo patientInquirerInfo = PatientInquirerInfo.builder()
                .patientId(patientId)
                .relation(InquirerRelationEnum.ONESELF.getValue())
                .name(addPatient.getNickName())
                .status(PatientInquirerInfo.STATUS_DEFAULT)
                .childTag(PatientInquirerInfo.CHILD_TAG_NO)
                .createdAt(new Date())
                .build();
            patientInquirerRepository.save(patientInquirerInfo);
        }

    }

    @Override
    public Long getPatientIdByMiniOpenId(String miniOpenId) {
        Patient patient = patientRepository.findFirstByMiniOpenId(miniOpenId);
        if (null == patient) {
            return null;
        }
        return patient.getId();
    }

    @Async
    void sendImSysMessage(Long doctorId, Long patientId) throws BusinessException {
        // 首次绑定缓存首次绑定的key, 有效期24小时
        int firstBindExpire = ConfigUtil.getInt("first.bind.expire");
        DoctorCharge doctorCharge = doctorChargeRepository.findFirstByDoctorIdAndChargeType(doctorId, DoctorCharge.CHARGE_TYPE_INQUIRING);
        RedisUtil.valueOps().set(CacheConstant.getFirstBindKey(doctorId, patientId), doctorCharge.getCharge() + "", firstBindExpire * 3600);
        PatientInquirerBO inquirerBO = patientInquirerService.getDefaultInquirerInfoByPatientId(null, patientId);
        PatientMsgSendParamBO patientMsgSendParamBO = new PatientMsgSendParamBO();
        patientMsgSendParamBO.setMsgType(ImConstants.MESSAGE_TYPE_PATIENTS_FIRST_BIND);
        patientMsgSendParamBO.setDoctorId(doctorId);
        patientMsgSendParamBO.setPatientId(patientId);
        patientMsgSendParamBO.setInquirerId(inquirerBO.getInquirerId());
        if (null != doctorCharge && doctorCharge.getCharge() > 0) {
            patientMsgSendParamBO.setContent(String.format(ConfigUtil.getString("first.bind.paid.text"), doctorCharge.getCharge() / 100, firstBindExpire));
        } else {
            patientMsgSendParamBO.setContent(String.format(ConfigUtil.getString("first.bind.free.text"), firstBindExpire));
        }
        imFeginClient.sendPatientSysMessage(patientMsgSendParamBO);

        // 就诊人未实名的逻辑
        if ((inquirerBO.getChildTag() == 1 && null == inquirerBO.getGuardianIdCard())
            || (inquirerBO.getChildTag() != 1 && null == inquirerBO.getIdCard())) {
            MsgSendParamBO sendParamBO = new MsgSendParamBO();
            sendParamBO.setMsgType(ImConstants.MESSAGE_TYPE_FIRST_BIND_UNNAMED);
            sendParamBO.setRelation(ImConstants.MESSAGE_RESOURCE_DOCTOR);
            sendParamBO.setDoctorId(doctorId);
            sendParamBO.setPatientId(patientId);
            sendParamBO.setInquirerId(inquirerBO.getInquirerId());
            sendParamBO.setExtId(inquirerBO.getInquirerId().toString());
            sendParamBO.setVisualType(ImConstants.MESSAGE_VISUAL_TYPE_PATIENT);
            sendParamBO.setSysMsgType(ImConstants.SYS_MSG_TYPE_SYSTEM);
            sendParamBO.setLastPlatform(ImConstants.HEADER_ORIGIN_PATIENT_APP);
            sendParamBO.setContent("请先完善就诊人信息，医生才可以为您开具处方，");
            // 向患者发送系统消息
            imFeginClient.sendSysMessage(sendParamBO);

            // 向医生发送系统消息
            sendParamBO.setRelation(ImConstants.MESSAGE_RESOURCE_PATIENT);
            sendParamBO.setVisualType(ImConstants.MESSAGE_VISUAL_TYPE_DOCTOR);
            sendParamBO.setContent("当前就诊人信息不完善，暂不能添加病历或处方，<a href='%s'><font color='#33b5e5'>提示患者完善信息</font></a>");
            imFeginClient.sendSysMessage(sendParamBO);
        }
    }

    @Override
    public void sendSignInMsg(String openId) {
        customMsgApiService.sendSystemMsg(openId, ConfigKeyUtil.getString("wachat.signin.ehp.message"));
    }
}
